<?php
ArrowController::importModel("controls.ArrowNode");
ArrowController::importModel("controls.ArrowTag");
ArrowController::importModel("controls.ArrowTagContainer");
ArrowController::importModel("controls.common.string.ArrowTagSimpleString");

class ArrowTemplateParser  extends ArrowObject{

    /**
     * Singleton
     *
     * @var object
     */
    private static $selfInstance = false;



    /**
     * Zarejestrowane tagi dla pojedyńczych znaczników.
     * 
     * @var array
     */
    private $registredTags = array ();

    private $rootNode = null;
    
    private $nodesList = array();
    
    private $hlpTagRepCount = array();
    //narazie nie musi byc globalna
    //private $hlpTagsIndexes = array();
    
    private $hlpCurrParent;
    
    
    private static $initInstructions = "";
    private static $setupInstructions = "";//\$ts = ArrowView::getCurrentView()->getTemplateSystem(); \n\$templateId = ArrowView::getCurrentView()->getTemplateDescriptor()->getKey();\n\n";
    private static $preInitInstructions = "";
    private $parsedCode = "";
    
    private $partsToParse = array();


    /**
     * Czy ignorować niezdefniowane tagi.
     * 
     * @var boolean
     */
    public $ignoreUnknowTags = true;

    /**
     * Return object instance
     *
     * @return ArrowTemplateParser
     */
    public static function getDefault() {
        if (self::$selfInstance == false){
            self::$selfInstance = new ArrowTemplateParser();
            self::$selfInstance->rootNode = new ArrowTagContainer(array(), "root", "root",  ArrowView::getCurrentView()->getTemplateDescriptor()->getKey());
        }
        return self :: $selfInstance;
    }
    
    /**
     * Constructor loads parsers
     */
    public function __construct(){
        $parserConf = ArrowConfProvider::getDefault()->getConf(ArrowConfProvider::TEMPLATE_PARSER);
        $tmpInstances = array();        
        foreach( $parserConf as $parser ){
            $this->registredTags[$parser["tag"][0]][$parser["tag"][1]] = array( "model" => $parser["model"], "method" =>$parser["method"]);
        }
    }
        
    private function getTagConf( $prefix, $name ){
        $class = false;
        $routeMethod = false;
        $possibleTags = array( $name, "*" );
        foreach($possibleTags as $tag){
            if(isset($this->registredTags[$prefix][$tag])){
                $tagConf = $this->registredTags[$prefix][$tag];
                $tmp = explode(".",$tagConf["model"]);
                $class = array_pop($tmp);
                
                if(!empty($tagConf["method"])){
                    $routeMethod = $tagConf["method"];
                }
                break;
            }    
        }
        if(!$class)
            throw new ArrowCoreException("Cat't find configuration for $prefix:$name tag");
            
        return array( "model" => $tagConf["model"], "class" => $class, "routeMethod" => $routeMethod );
    }

    private function getRegInterpretation($regResult, $withIndex){
        if( $withIndex )
            return  array(
                    "startIndex" => $regResult[0][1],
                    "endIndex" => $regResult[5][1]+1,
                    "type" => (empty($regResult[5][0]))?"block":"tag",
                    "option" => (empty($regResult[1][0]))?"open":"close",
                    "prefix" => $regResult[2][0],
                    "name" => $regResult[3][0],
                    "args" =>  $this->prepareArguments($regResult[4][0])
                );
        else
            return  array(
                    "type" => (empty($regResult[5]))?"block":"tag",
                    "option" => (empty($regResult[1]))?"open":"close",
                    "prefix" => $regResult[2],
                    "name" => $regResult[3],
                    "args" =>  $this->prepareArguments($regResult[4])
                );
    }
    private static $count = 1;
    private function createObject( $regInterpretation, $tagConfiguration ){
        $t = $regInterpretation;
        $baseClass = ($regInterpretation["type"] == "blok")?"ArrowTagContainer":"ArrowTag";
        $class = ($tagConfiguration["class"])?$tagConfiguration["class"]:$baseClass;
        if($tagConfiguration["routeMethod"]){
            $instance = ArrowController::getModelInstance($tagConfiguration["model"]);
            
            $tagSet = call_user_func_array( 
                array($instance, $tagConfiguration["routeMethod"]),
                array( $t["args"], $t["prefix"], $t["name"], $class  )
            );
            
            if( is_string( $tagSet ) ){
                $newObj = new ArrowTagSimpleString($t["args"], $t["prefix"], $t["name"],  ArrowView::getCurrentView()->getTemplateDescriptor()->getKey(), "ArrowTagSimpleString");
                $newObj->setOutputString($tagSet);
                return $newObj;
            }else{
                $this->appendSetupModel($tagSet["model"]);
                $tagSet["object"]->configure();
                $this->addControlInformation($t["prefix"], $t["name"], $tagSet["object"], $instance);
                return $tagSet["object"];
            }
            
        }else{
            ArrowController::importModel($tagConfiguration["model"]);
            $obj = new $class( $t["args"], $t["prefix"], $t["name"], ArrowView::getCurrentView()->getTemplateDescriptor()->getKey(), $class );
            $obj->configure();
            $this->addControlInformation($t["prefix"], $t["name"], $obj);
            return $obj;
        }    
    }
    
    const MATCH_ALL_MODE = 1;
    const MARCH_TARGETS_MODE = 2;
    
    private $parserMode = self::MATCH_ALL_MODE;
    private $parserTargets = self::MATCH_ALL_MODE;
    public function setTargetTags( $targets  ){
        $this->parserMode = self::MARCH_TARGETS_MODE;
        $this->parserTargets = $targets;
    }

    
    /**
     * Parsuje string.
     * 
     * @param string $string String przeznaczony do parsowania.
     * @return sting Ciąg z pozamienianymi blokami i znacznikami.
     */
    public function parseString($string, $root = false, $initRoot = true) {
        //ArrowNode::$index = 0;
        $this->hlpTagsIndexes = array();
        if(!$root)
            $this->rootNode = new ArrowTagContainer(array(), "root", "root","ArrowTagContainer");
        else
            $this->rootNode = $root;
            
            
        /*
        	self::$initInstructions = "";
        	self::$preInitInstructions = "";
        	self::$setupInstructions = "";
        
       	if(empty(self::$setupInstructions))
        	self::$setupInstructions = "\$ts = ArrowView::getCurrentView()->getTemplateSystem(); \n\$templateId = ArrowView::getCurrentView()->getTemplateDescriptor()->getKey();\n\n";
*/        
        $this->hlpTagRepCount = array();
        $this->hlpCurrParent = null;
        $this->parsedCode = $string;
                
        
        $currParent = $this->rootNode;
        $tags = array ();
        $reg = "/<(\/|)([\w\-]+):(\w+)(.*?)(\/|)>/ms";

        
        $blocksOpened = 0;
        $blocksClosed = 0;
        $string = str_replace("?>", "_arrow_end_delim_", $string );
        
        
        if($initRoot){
        	$this->appendSetupInstructions($this->rootNode);
        	$this->appendInitInstructions($this->rootNode);
        }
        
        
        if(!$root){
            $hlpTagsIndexes = array();
            
            preg_match_all($reg, $string, $tags, PREG_SET_ORDER|PREG_OFFSET_CAPTURE);
            
            foreach( $tags as $index => $tag ){
                    
                $t = $this->getRegInterpretation($tag, true);
                
                if($this->parserMode == self::MARCH_TARGETS_MODE){
                    if( !in_array( $t["prefix"].":".$t["name"], $this->parserTargets ) ){
                        continue;
                    }
                }
                
                $conf = $this->getTagConf($t["prefix"], $t["name"]);
                $this->appendSetupModel($conf["model"]);
            
                $ignore = 0;
                
                if($t["type"] == "block" && $t["option"] == "open"){
                    $currBlock = $this->createObject( $t, $conf );
                    /*if($currBlock->isDisabled())
                        continue;*/
                    $currBlock->setParent($currParent);
                    $currParent = $currBlock;
                    $blocksOpened++;
                    $hlpTagsIndexes[$currBlock->getNodeIndex()]["contentStart"] = $t["endIndex"];
                    $this->appendSetupInstructions($currBlock);
                    if($currBlock->isContentCodeKept())
                    	$ignore++;
                }elseif($t["type"] == "block" && $t["option"] == "close"){
                	
                	if($currBlock->isContentCodeKept())
                    	$ignore--;
                	
                    $currParent = $currBlock->getParent();
                    $blocksClosed++;
                    $hlpTagsIndexes[$currBlock->getNodeIndex()]["contentStop"] = $t["startIndex"];
                    
                    if($currBlock->isContentCodeKept()){
                        $offsetArr = $hlpTagsIndexes[$currBlock->getNodeIndex()];
                        $code = substr( $string, $offsetArr["contentStart"], $offsetArr["contentStop"]-$offsetArr["contentStart"]);
                        $currBlock->setContentCode($code);
                    }
                    $this->appendInitInstructions($currBlock);
                    $currBlock = $currBlock->getParent();
                }else{
                	$currEl = $this->createObject( $t, $conf );
                	if($ignore > 0 )
                		continue;
                    
                    $currEl->setParent($currParent);
                    $this->appendSetupInstructions($currEl);
                    $this->appendInitInstructions($currEl);
                }
            }
            
            
            if($blocksOpened != $blocksClosed)
                exit("nieotwarty lub niedomknięty blok");
        }
        
        $this->hlpCurrParent = $this->getRootNode();
        $this->hlpTagRepCount = array(0);
        $source = preg_replace_callback($reg, array (
                     $this,
                    'replaceNode'
                ), $string);
                
        $source = str_replace( "_arrow_end_delim_", "?>", $source );        
        
        $this->parsedCode = $source;
        
        $this->parserMode = self::MATCH_ALL_MODE;
        
        /*foreach($this->partsToParse as $el){
            $code = $this->parseString($el[1],$el[0]);
            $currBlock->setContentCode($this->getParsedCode());
            $this->appendInitInstructions($currBlock);
        }*/
    }
    
    public function clearCache(){
    	
       	self::$initInstructions = "";
       	self::$preInitInstructions = "";
       	self::$setupInstructions = "";
        
       	if(empty(self::$setupInstructions))
        	self::$setupInstructions = "\$ts = ArrowView::getCurrentView()->getTemplateSystem(); \n\$templateId = ArrowView::getCurrentView()->getTemplateDescriptor()->getKey();\n\n";
    	
    }

    public function replaceNode($tag){
            $t = $this->getRegInterpretation($tag, false);
            if($this->parserMode == self::MARCH_TARGETS_MODE){
                if( !in_array( $t["prefix"].":".$t["name"], $this->parserTargets ) ){
                    return $tag[0];
                }
            }
            if($t["type"] == "block" && $t["option"] == "open"){
                $children = $this->hlpCurrParent->getChildren();
                $currEl = $children[end($this->hlpTagRepCount)];
                $this->hlpTagRepCount[] = 0;
                $this->hlpCurrParent = $currEl;
                if($currEl->isDisabled()) return "";
                return $currEl->generateBlockStartCode();    
                
            }elseif($t["type"] == "block" && $t["option"] == "close"){
                $currEl = $this->hlpCurrParent;
                $this->hlpCurrParent = $currEl->getParent();
                array_pop($this->hlpTagRepCount);
                $keys = array_keys($this->hlpTagRepCount);
                $lastkey = end($keys);
                $this->hlpTagRepCount[$lastkey]++;
                if($currEl->isDisabled()) return "";
                return $currEl->generateBlockEndCode( );
            }else{
                $children = $this->hlpCurrParent->getChildren();
                $currEl = $children[end($this->hlpTagRepCount)];
                $keys = array_keys($this->hlpTagRepCount);
                $lastkey = end($keys);
                $this->hlpTagRepCount[$lastkey]++;
                if($currEl->isDisabled()) return "";
                return $currEl->generateOutputCode();
            }

    }
    
    public function getParsedCode(){
        return $this->parsedCode;
    }
    
    public function appendStrSetupInstructions( $string){
        self::$setupInstructions.="\n\n".$string;
    }
    
    public function appendSetupInstructions( ArrowNode $node){
        self::$setupInstructions.="\n\n//".$node."\n\n".$node->getSetupInstructions();
    }
    
    private $setupModels = array();
    public function appendSetupModel( $model ){
        $this->setupModels[] = $model;
    }
    
    public function appendInitInstructions( ArrowNode $node){
        if(!$node->isDisabled()){
            self::$initInstructions.="\n\n//".$node."\n\n".$node->getInitInstructions();
            self::$preInitInstructions.= "\n\n//".$node."\n\n".$node->getPreInitInstructions();
            
        }
    }
    
    public function getSetupInstructions(){
        $str = "";
        $this->setupModels = array_unique($this->setupModels);
        foreach($this->setupModels as $model)
            $str.="ArrowController::importModel(\"{$model}\");\n" ;
            
        /*    
        $code = "";
        foreach( $this->setupModels as $model ){
            $path = ArrowController::getModelPath($model);
            $file = file_get_contents($path);
            $file = preg_replace( "/\_\_FILE\_\_/", "'".$path."'", $file );
            $file = preg_replace( "/ArrowController *?:: *?importModel.+?\;/", "", $file );
            $file = preg_replace( "/^[\s]*?<\?(php| *?)/ms", "", $file );
            $file = preg_replace( "/\?>[\s]*$/ms", "", $file );
            $file = preg_replace( "/\?>[\s]*$/ms", "", $file );
            
            $code.="\n".$file;
        }
        $code =.$code."\n?>";
        */        
        
        return $str.self::$setupInstructions;
    }
    public function getInitInstructions(){
        return self::$initInstructions;
    }
    
    public function getPreInitInstructions(){
    	return self::$preInitInstructions;
    }
    
    public function getRootNode(){
        return $this->rootNode;
    }


    /**
     * Zwraca argumenty znacznika.
     * 
     * @param string $string Ciag znaków zawarty w znaczniku.
     */
    function prepareArguments($string) {
        $string = trim($string);
        if (empty ($string))
            return array ();

        $regs = array ();

        preg_match_all("/([\w\-]*?)=\"(.*?)\"/", $string, $regs, PREG_PATTERN_ORDER);

        $args = array_combine($regs[1], $regs[2]);
        
        foreach($args as $key => $val)
            $args[$key] = addslashes($val);

        return $args;
    }
    //TODO implement
    private function addControlInformation($prefix, $name , $obj, $router = false){
        return;
        
        static $configured = array();
        if( $this->parserMode == self::MATCH_ALL_MODE && !$obj->isDisabled()){
            //if( !isset($configured[$prefix.$name] )){
                $configured[$prefix.$name] = true;
                FB::log($prefix . " ". $name );
            //}
        }
    }
    
    public  function getControlsInformation(){
    	$conf = $this->registredTags;
    	$tmp = array();
    	foreach($conf as $prefix => &$prefixSet){
    		foreach($prefixSet  as $tag => $val){
	    		if($val["method"]){
	            	$instance = ArrowController::getModelInstance($val["model"]);
	            	/*if($instance instanceof IArrowControlsManager){
	            		$versions = $instance->getControlVersionsConf($prefix,$tag);
	            		if(isset($tmp[$prefix])){
	            			if( !is_array($versions)){
	            			}else
	            			$tmp[$prefix] = array_merge($tmp[$prefix], $versions);
	            		}else
	            			$tmp[$prefix] = $versions;
	            	}*/
	    		}else{
					ArrowController::importModel($val["model"]);
					$_tmp = explode(".",$val["model"]);
                	$class = array_pop($_tmp);
            		$obj = new $class( array(), $prefix, $tag, $class );
            		$obj->configure();
            		
            		$tmp[$prefix][$tag] = array(
            			"params" => $obj->getProperties(),
            			"requiredParams" => $obj->getRequiredProperties()
            			
            		);
	            }
    		}
    	}

    	return $tmp;
    }
    
    public function generateControlsInformationXml(){
    	$result = ArrowTemplateParser::getDefault()->getControlsInformation();
    	$xml = "<controls-conf>";
		foreach( $result as $prefix => $tag ){
	    	foreach($tag as $name => $val ){
	        	$default = "";
	        	if(isset($val["default"])) $default = " ".$val["default"]." ";
	        	$xml.= '<tag prefix="'.$prefix.'" name="'.$name.'" default="'.htmlentities($default).'" >';    
	        	foreach($val["params"] as $paramName => $paramValue){
	            	if( is_object($paramValue)) $paramValue = "";
	            	$required = 0;
	            	if(isset($val["requiredParams"]))
	            	if(in_array($paramName, $val["requiredParams"])) $required = 1;
	            
	            	$xml.='<param name="'.$paramName.'" value="'.$paramValue.'" required="'.$required.'" />';
	        	}
	        $xml.= '</tag>';
	    	}
    	
		}
		$xml.= "</controls-conf>";
		file_put_contents( "./contr.xml", $xml);
    	
    
    }


}




?>